#ifndef __SPI_H__
#define __SPI_H__

#include <Arduino.h>
extern "C"
{
#include "utility/spi_com.h"
}

#define SPI_TRANSMITRECEIVE 0x0
#define SPI_TRANSMITONLY 0x1

// Compatibility with sketches designed for AVR @ 16 MHz could not
// be ensured as SPI frequency depends of system clock configuration.
// user have to use appropriate divider for the SPI clock
// This function should not be used in new project.
// Use SPISettings with SPI.beginTransaction() to configure SPI parameters.
#define SPI_CLOCK_DIV2   2
#define SPI_CLOCK_DIV4   4
#define SPI_CLOCK_DIV8   8
#define SPI_CLOCK_DIV16  16
#define SPI_CLOCK_DIV32  32
#define SPI_CLOCK_DIV64  64
#define SPI_CLOCK_DIV128 128

#define SPI_MODE0 0x00
#define SPI_MODE1 0x01
#define SPI_MODE2 0x02
#define SPI_MODE3 0x03

enum SPITransferMode
{
    SPI_CONTINUE, /* Transfer not finished: CS pin kept active */
    SPI_LAST      /* Transfer ended: CS pin released */
};

#define CS_PIN_CONTROLLED_BY_USER NUM_DIGITAL_PINS

#define NO_CONFIG ((int16_t)(-1))

#ifndef NB_SPI_SETTINGS
#define NB_SPI_SETTINGS 4
#endif

#ifndef SPI_TRANSFER_TIMEOUT
#define SPI_TRANSFER_TIMEOUT 1000
#endif

class SPISettings
{
    friend class SPIClass;

public:
    SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode, bool noRecv = SPI_TRANSMITRECEIVE)
    {
        clk = clock;
        bOrder = bitOrder;
        noReceive = noRecv;

        if (SPI_MODE0 == dataMode)
        {
            dMode = SPI_MODE_0;
        }
        else if (SPI_MODE1 == dataMode)
        {
            dMode = SPI_MODE_1;
        }
        else if (SPI_MODE2 == dataMode)
        {
            dMode = SPI_MODE_2;
        }
        else if (SPI_MODE3 == dataMode)
        {
            dMode = SPI_MODE_3;
        }
    }

    SPISettings()
    {
        pinCS = -1;
        clk = SPI_SPEED_CLOCK_DEFAULT;
        bOrder = MSBFIRST;
        dMode = SPI_MODE_0;
    }

private:
    int16_t pinCS;
    uint32_t clk;
    BitOrder bOrder;
    spi_mode_e dMode;

    bool noReceive;
};

class SPIClass
{
public:
    SPIClass();
    SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel = (uint8_t)NC);

    void setMISO(uint32_t miso)
    {
        _spi.pin_miso = digitalPinToPinName(miso);
    };
    void setMOSI(uint32_t mosi)
    {
        _spi.pin_mosi = digitalPinToPinName(mosi);
    };
    void setSCLK(uint32_t sclk)
    {
        _spi.pin_sclk = digitalPinToPinName(sclk);
    };
    void setSSEL(uint32_t ssel)
    {
        _spi.pin_ssel = digitalPinToPinName(ssel);
    };

    void setMISO(PinName miso)
    {
        _spi.pin_miso = (miso);
    };
    void setMOSI(PinName mosi)
    {
        _spi.pin_mosi = (mosi);
    };
    void setSCLK(PinName sclk)
    {
        _spi.pin_sclk = (sclk);
    };
    void setSSEL(PinName ssel)
    {
        _spi.pin_ssel = (ssel);
    };

    void begin(uint8_t _pin = CS_PIN_CONTROLLED_BY_USER);
    void end(void);

    void beginTransaction(uint8_t pin, SPISettings settings);
    void beginTransaction(SPISettings settings)
    {
        beginTransaction(CS_PIN_CONTROLLED_BY_USER, settings);
    }

    void endTransaction(uint8_t pin);
    void endTransaction(void)
    {
        endTransaction(CS_PIN_CONTROLLED_BY_USER);
    }

    byte transfer(uint8_t pin, uint8_t _data, SPITransferMode _mode = SPI_LAST);
    uint16_t transfer16(uint8_t pin, uint16_t _data, SPITransferMode _mode = SPI_LAST);
    void transfer(uint8_t pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST);
    void transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST);

    byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST)
    {
        return transfer(CS_PIN_CONTROLLED_BY_USER, _data, _mode);
    }

    uint16_t transfer16(uint16_t _data, SPITransferMode _mode = SPI_LAST)
    {
        return transfer16(CS_PIN_CONTROLLED_BY_USER, _data, _mode);
    }

    void transfer(void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST)
    {
        transfer(CS_PIN_CONTROLLED_BY_USER, _buf, _count, _mode);
    }

    void transfer(void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST)
    {
        transfer(CS_PIN_CONTROLLED_BY_USER, _bufout, _bufin, _count, _mode);
    }

    void setBitOrder(uint8_t _pin, BitOrder);
    void setBitOrder(BitOrder _order)
    {
        setBitOrder(CS_PIN_CONTROLLED_BY_USER, _order);
    }

    void setDataMode(uint8_t _pin, uint8_t);
    void setDataMode(uint8_t _mode)
    {
        setDataMode(CS_PIN_CONTROLLED_BY_USER, _mode);
    }

    void setClockDivider(uint8_t _pin, uint8_t);
    void setClockDivider(uint8_t _div)
    {
        setClockDivider(CS_PIN_CONTROLLED_BY_USER, _div);
    }

private:
    typedef enum
    {
        GET_IDX = 0,
        ADD_NEW_PIN = 1
    } pin_option_t;

    uint8_t pinIdx(uint8_t _pin, pin_option_t option)
    {
        uint8_t i;

        if (_pin > NUM_DIGITAL_PINS)
        {
            return NB_SPI_SETTINGS;
        }

        for (i = 0; i < NB_SPI_SETTINGS; i++)
        {
            if (_pin == spiSettings[i].pinCS)
            {
                return i;
            }
        }

        if (option == ADD_NEW_PIN)
        {
            for (i = 0; i < NB_SPI_SETTINGS; i++)
            {
                if (spiSettings[i].pinCS == -1)
                {
                    spiSettings[i].pinCS = _pin;
                    return i;
                }
            }
        }
        return i;
    }

    void RemovePin(uint8_t _pin)
    {
        if (_pin > NUM_DIGITAL_PINS)
        {
            return;
        }

        for (uint8_t i = 0; i < NB_SPI_SETTINGS; i++)
        {
            if (spiSettings[i].pinCS == _pin)
            {
                spiSettings[i].pinCS = -1;
                spiSettings[i].clk = SPI_SPEED_CLOCK_DEFAULT;
                spiSettings[i].bOrder = MSBFIRST;
                spiSettings[i].dMode = SPI_MODE_0;
            }
        }
    }

    void RemoveAllPin(void)
    {
        for (uint8_t i = 0; i < NB_SPI_SETTINGS; i++)
        {
            spiSettings[i].pinCS = -1;
            spiSettings[i].clk = SPI_SPEED_CLOCK_DEFAULT;
            spiSettings[i].bOrder = MSBFIRST;
            spiSettings[i].dMode = SPI_MODE_0;
        }
    }

private:
    SPISettings spiSettings[NB_SPI_SETTINGS];
    int16_t _CSPinConfig;
    spi_t _spi;
};

extern SPIClass SPI;

#endif // __SPI_H__